import { controller } from '@/engine/Controller';
import { ChartsService } from "../ChartsService";
import * as echarts from "echarts";
import { throttle } from "lodash";
import { ModelInfo, SeriesColors, ShowVariableDataStatus, TimeChartUiConfig, TimeSeryChartConfigs, TimeSeryUpdate, UrlParam } from "@/engine/types/charts";
import { PropertyKey } from "@/constants/constants";
import { storage } from '@/util/common';
import { StorageKey } from "@/engine/types/Controller";
import { SimulateStatusType } from "@/engine/_";
import { dealDataForChartxAxis } from "@/util/common";
import { SimSubwinMsgToSimWin } from "@/engine/types/Win";
import { markRaw } from 'vue';
export class TimeSeryService {

  chartsService: ChartsService;
  chartInstance = {} as echarts.EChartsType
  ChartDataLength = Math.pow(2, 15)
  FilterTimes = 0;
  FilterLastIndex = 0
  FilterRecordNum = 0
  uiId = ''
  updateByUiIdTimer = 0
  updateByVariablePanelTimer = 0
  timesRecord = 0
  simClockRatio = 1
  simStepSize = 1000
  isFmu = false
  simStartTime = null
  showValueModels = [] as string[]
  showVariableDataStatus: Map<string, ShowVariableDataStatus[]> = new Map()
  timeSeryChartSeriesMap: Map<string, TimeSeryChartSeries> = new Map()
  visualMapClassMap: Map<string, VisualMapClass> = new Map()
  variableModelInfo: Map<string, ModelInfo> = new Map()
  uiConfig = {} as TimeChartUiConfig
  config = markRaw({
    animation: false,
    title: {
      left: "center",
      text: ''
    },
    tooltip: {
      trigger: "item",
      axisPointer: {
        type: "cross",
        snap: true, // 辅助轴会自动吸附到最近的点上
        animation: false,
      },
      formatter: (params: any) => {
        let r = '';
        if (params.value && params.value.length) {
          r = `
            ${params.seriesName}<br />
            ${this.chartsService.controller.locale.localeTrans('s_time')}: ${params.value[0]}<br />
            ${this.chartsService.controller.locale.localeTrans('v_value')}: ${params.value[1]}<br />
            ${String(this.uiConfig[PropertyKey.TimeSeriesChart_annotateFailures]?.value) === 'false' ? '' : params.name}
          `;
        }
        return r;
      },
    },
    dataZoom: [
      {
        type: "slider",
        show: true,
        xAxisIndex: [0],
        filterMode: "none"
      },
      {
        type: "inside",
        xAxisIndex: [0],
        filterMode: "none"
      },
      {
        type: "slider",
        show: true,
        yAxisIndex: [0],
        left: "93%",
        filterMode: "none"
      },
      
      {
        type: "inside",
        yAxisIndex: [0],
        filterMode: "none"
      }
    ],
    xAxis: {
      type: "value",
      name: '',
      splitLine: {
        show: true
      },
    },
    yAxis: {
      type: "value",
      name: '',
      splitLine: {
        show: true
      }
    },
    series: [] as any,
    toolbox: {
      show: true,
      feature: {
        saveAsImage: {
          show: true,
          title: '',
          icon: "image://statics/icons/app/chart_download.svg",
          onclick: () => {}
        },
      }
    },
  }) as any

  chartOptionChangeToRender = throttle(() => {
    this.chartInstance.setOption(this.config, true, true);

  }, 100);

  constructor(c: ChartsService) {
    this.chartsService = c;
  }

  serviceInit() {
    if (this.updateByVariablePanelTimer) {
      clearInterval(this.updateByVariablePanelTimer);
      this.updateByVariablePanelTimer = 0;
    }
    if (this.updateByUiIdTimer) {
      clearInterval(this.updateByUiIdTimer);
      this.updateByUiIdTimer = 0;
    }
    this.timesRecord = 0;
    this.showVariableDataStatus.clear();
  }

  async init(p: UrlParam) {
    this.serviceInit();
    this.config.xAxis.name = this.chartsService.controller.locale.localeTrans('sim_clock');
    this.config.toolbox.feature.saveAsImage.title = this.chartsService.controller.locale.localeTrans('_save_as_picture');
    let isLinearInterpolation = false;
    if (p.uiId) { // opened by sim-ui
      this.uiId = p.uiId;
      this.isFmu = await this.isFmuSim(p.uiId, p.projectId);
      // get show value
      const timeChartUiConfig = this.uiConfig = (await this.getTimeSeryChartConfigs(p.uiId, p.projectId!)).data?.data;
      if (timeChartUiConfig && timeChartUiConfig[PropertyKey.SelectPropertiesConfig_value]?.value.length) {
        this.showValueModels = timeChartUiConfig[PropertyKey.SelectPropertiesConfig_value].value;
        isLinearInterpolation = String(timeChartUiConfig[PropertyKey.TimeSeriesChart_LineraInterpolation]?.value) === 'true';
        await this.dealTimeChartUiConfig(timeChartUiConfig, p);
      }
      console.log(timeChartUiConfig, 'timeChartUiConfig');
      // is keep win open?
      if (this.uiConfig[PropertyKey.TimeSeriesChart_keepOpenAfterTermination]?.value) {
        this.chartsService.controller.winService.simSubWinSendInfoToSimWin({ type: SimSubwinMsgToSimWin.TimeSeryKeepOpen });
      }
    } else { // opened by variable panel directly
      this.showValueModels = [''];
      this.config.title.text = p.title!;
    }

    // init time sery ui
    const chartBox = document.getElementById('box_id');
    if (!chartBox) return;
    const timeSery = this.chartInstance = markRaw(echarts.init(chartBox));
    this.dealConfigSeries(this.showValueModels, isLinearInterpolation, this.uiConfig[PropertyKey.TimeSeriesChart_annotateFailures]?.value);
    timeSery.setOption(this.config);

    window.addEventListener('resize', throttle(() => timeSery.resize()));
    this.renderChartAndWaitForUpdate(p.projectId!, p.modelId!, p.simulationSessionId!, p.uiId);
  }

  async isFmuSim(modelId?: string, projectId?: string) {
    let r = false;
    if (!(modelId && projectId)) return r;
    const seryConfigRepresentInfo = await this.chartsService.controller.modelApi.modelPropertyByKey({ modelId, projectId, key: PropertyKey.SelectPropertiesConfigRepresents });
    console.log(seryConfigRepresentInfo, 'seryConfigRepresentInfo');
    const { value } = seryConfigRepresentInfo;
    if (value.sts && value.sts.length) {
      for (const s of value.sts) {
        if (s.includes('Fmu')) {
          r = true;
          break;
        }
      }
    }
    return r;
  }

  async dealTimeChartUiConfig(config: TimeChartUiConfig, p: UrlParam) {

    // title 
    if (config[PropertyKey.TimeSeriesChart_Title].value) {
      this.config.title.text = config[PropertyKey.TimeSeriesChart_Title].value;
    } else {
      this.config.title.text = await this.getChartTitle(p.projectId!, this.showValueModels);
    }

    // yLabel
    if (config[PropertyKey.TimeSeriesChart_YLabel].value) {
      this.config.yAxis.name = config[PropertyKey.TimeSeriesChart_YLabel].value;
    } else {
      this.config.yAxis.name = await this.getChartTitle(p.projectId!, this.showValueModels);
    }
    // this.config.yAxis.name += '1';

    // refreshRate

    // x and y split-line
    if (String(config[PropertyKey.TimeSeriesChart_gridX]?.value) === 'false') {
      this.config.xAxis.splitLine.show = false;
    }
    if (String(config[PropertyKey.TimeSeriesChart_gridY]?.value) === 'false') {
      this.config.yAxis.splitLine.show = false;
    }

    // xAxis: fixedRange server-error
    // yAxis: min && max
    const minValue = Number(config[PropertyKey.TimeSeriesChart_minValue]?.value);
    const maxValue = Number(config[PropertyKey.TimeSeriesChart_maxValue]?.value);
    if (minValue && maxValue) {
      if (maxValue > minValue) {
        this.config.yAxis.min = minValue;
        this.config.yAxis.max = maxValue;
      } else {
        this.config.yAxis.max = maxValue;
      }
    } else {
      if (!isNaN(minValue)) {
        this.config.yAxis.min = minValue;
      } else if (!isNaN(maxValue)) {
        this.config.yAxis.max = maxValue;
      }
    }
  }

  async renderChartAndWaitForUpdate(projectId: string, modelId: string, simulationSessionId: string, uiId?: string) {
    // get internal sim-clock config
    const simClockRelateRes = (await this.chartsService.controller.modelApi.modelPropertyListByKeys({
      projectId: projectId,
      modelId: modelId,
      keys: [
        PropertyKey.SimulationConfig_ClockRatio,
        PropertyKey.SimulationConfig_StartTime,
        PropertyKey.SimulationConfig_StepSize,
      ]
    })).data?.data;
    if (simClockRelateRes) {
      for (const scd of simClockRelateRes) {
        switch (scd.key) {
          case PropertyKey.SimulationConfig_StartTime:
            this.simStartTime = isNaN(Number(scd.value)) ? null : scd.value;
            break;
          case PropertyKey.SimulationConfig_ClockRatio:
            this.simClockRatio = scd.value || 1;
            break;
          case PropertyKey.SimulationConfig_StepSize:
            this.simStepSize = scd.value || 1000;
            break;
          default:
        }
      }
    }
    if (uiId) { // opened by sim-ui
      // get primary data
      this.updateTimeSeryChart({
        simulationSessionId: simulationSessionId,
        times: this.timesRecord,
      });
      // if (!this.isFmu) { // update auto
      //   // const usedStepNum = Math.round(Number(this.simStepSize) / Number(this.simClockRatio));
      //   const usedStepNum = this.uiConfig[PropertyKey.TimeSeriesChart_refreshRate]?.value || 1000;
      //   this.updateByUiIdTimer = window.setInterval(() => {
      //     if (this.chartsService.simStatus === SimulateStatusType.running) {
      //       this.updateTimeSeryChart({
      //         simulationSessionId: simulationSessionId,
      //         times: this.timesRecord,
      //       });
      //       this.timesRecord += usedStepNum;
      //     }
      //   }, usedStepNum);
      // }
    }

    // record simClockRatio && simStepSize
    storage.set(StorageKey.IsTimeSeryMounted, {
      isMounted: true,
      simClockRatio: this.simClockRatio,
      simStepSize: this.simStepSize,
    });
  }

  update(v: string[]) {
    this.config.series[0].data.push(v);
    this.chartInstance.setOption(this.config);
  }

  updateTimeSeryChartStepOne() {
    const usedStepNum = Math.round(Number(this.simStepSize) / Number(this.simClockRatio));
    this.timesRecord += usedStepNum;
    this.updateTimeSeryChart({
      simulationSessionId: this.chartsService.wparam.simulationSessionId!,
      times: this.timesRecord,
    });
  }
  async updateTimeSeryChart(p: TimeSeryUpdate) {
    // if (this.isFmu && !p.consumers?.includes(this.uiId)) return;
    if (!p.consumers?.includes(this.uiId)) return;
    // if (this.isFmu) {
    if (p.consumers?.includes(this.uiId)) {
      this.FilterRecordNum++;
      for (const [index, sery] of this.config.series.entries()) {
        let isPush = true;
        if (this.FilterLastIndex) {
          isPush = false;
          if (this.FilterRecordNum - this.FilterLastIndex === Math.pow(2, this.FilterTimes)) {
            isPush = true;
            if (index === this.config.series.length - 1) {
              this.FilterLastIndex = this.FilterRecordNum;
            }
          }
          
        }
        if (isPush) {
          sery.data.push({
            name: '',
            value: [p.times, p.data[sery.name]]
          });
        }
        if (sery.data.length >= Math.ceil(this.ChartDataLength / this.config.series.length)) {
          sery.data = sery.data.filter((_: any, index: number) => !(index % 2));
          if (index === this.config.series.length - 1) {
            this.FilterTimes++;
            if (!this.FilterLastIndex) { // init
              this.FilterLastIndex = this.FilterRecordNum;
            }
          }
        }
      }
    }
    // } else {
    //   const resData = (await this.chartsService.controller.simApi.timeserieschartGetTscLineData({
    //     simulationSessionId: p.simulationSessionId,
    //     instanceId: this.chartsService.instanceId,
    //     propertyIds: this.showValueModels
    //   })).data?.data?.results; 
      
    //   if (resData && resData.length) {
    //     for (const d of resData) {
    //       for (const sery of this.config.series) {
    //         if (sery.modelId === d.propertyId && d.values[0]) {
    //           if (this.isFmu) {
    //             if (p.consumers?.includes(this.uiId)) {
    //               sery.data.push({
    //                 name: d.values[0]?.fullMsg,
    //                 value: [p.times, p.data[sery.name]]
    //               });
    //             }
    //           } else {
    //             sery.data.push({
    //               name: d.values[0]?.fullMsg,
    //               value: [p.times, d.values[0].value]
    //             });
    //           }
              
    //           // deal marker area
    //           let getShowVariableDataStatusData = this.showVariableDataStatus.get(d.propertyId);
    //           if (!getShowVariableDataStatusData) {
    //             getShowVariableDataStatusData = [];
    //           }
    //           getShowVariableDataStatusData.push({ x: p.times, status: d.values[0].status });
    //           this.showVariableDataStatus.set(d.propertyId, getShowVariableDataStatusData);
    //           this.createSeriesMarker(this.uiConfig[PropertyKey.TimeSeriesChart_annotateFailures].value);
    //           this.createVisualMappiecesBySeriesMarker(this.uiConfig[PropertyKey.TimeSeriesChart_annotateFailures].value);
    //           this.updateDealYAxisFixedRange(this.uiConfig[PropertyKey.TimeSeriesChart_fixedRange].value, d.values[0].value);
    //           this.dealTimeLengthAndLocation(p.times);
    //           break;
    //         }
    //       }
    //     }
    //   }
    // }
    console.log("chart config is: ", this.config);
    this.chartOptionChangeToRender();
  }

  getTimeSeryChartConfigs(uiId: string, projectId: string) {
    return this.chartsService.controller.modelApi.modelPropertyGetPropertyValuesById({
      projectId,
      modelId: uiId,
      propertyKeys: TimeSeryChartConfigs
    });
  }

  dealConfigSeries(models: string[], isLinearInterpolation: boolean, isAnnotateFailure = false) {
    // if (this.isFmu) {
    isAnnotateFailure = false;
    // }
    for (const [i, m] of models.entries()) {
      const seryName = this.variableModelInfo.get(m)?.name || '';
      const seryIns = new TimeSeryChartSeries({ name: seryName, modelId: m, index: i });
      if (!isLinearInterpolation) { // set linearInterpolation
        seryIns.step = `start-${i}`;
      }
      if (isAnnotateFailure) {
        const visualmap = new VisualMapClass({ index: i });
        this.visualMapClassMap.set(m, visualmap);
        this.timeSeryChartSeriesMap.set(m, seryIns);
        if (!this.config.visualMap) {
          this.config.visualMap = [] as VisualMapClass[];
        }
        this.config.visualMap.push(visualmap);
      }
      this.config.series.push(seryIns);
    }
  }

  async getChartTitle(projectId: string, modelIds: string[]) {
    const dealedTitle = [];
    const resData = (await this.chartsService.controller.modelApi.modelInfos({
      projectId,
      modelIds
    })).data?.data;
    if (resData && resData.length) {
      // create title and map the resdata
      for (const r of resData) {
        dealedTitle.push(r.name);
        this.variableModelInfo.set(r.id, r);
      }
    }
    return dealedTitle;
  }

  createSeriesMarker(isAnnotateFailure: boolean) {
    if (!isAnnotateFailure) return;
    for (const [k, v] of this.showVariableDataStatus.entries()) {
      const timeSeryChartSeriesMapData = this.timeSeryChartSeriesMap.get(k);
      if (!timeSeryChartSeriesMapData) return;
      timeSeryChartSeriesMapData.markArea.data = [];
      let flagData = []; // flagData.length === 2
      for (const [i, status] of v.entries()) {
        if (status.status === 'red') {
          if (!flagData.length) {
            flagData.push(status.x);
          } else {
            flagData[1] = status.x;
          }
          if (i === v.length - 1 && flagData.length === 2) {
            timeSeryChartSeriesMapData?.markArea.data.push([{ xAxis: flagData[0] }, { xAxis: flagData[1] }]);
            flagData = [];
          }
        } else if (status.status === 'green' && flagData.length) {
          if (flagData.length === 1) { // add marker area
            flagData[1] = flagData[0];
          }
          timeSeryChartSeriesMapData?.markArea.data.push([{ xAxis: flagData[0] }, { xAxis: flagData[1] }]);
          flagData = [];
        }
      }
    }
  }

  updateDealYAxisFixedRange(isFixedRange: boolean, yValue: number) {
    if (!isFixedRange) {
      if (this.config.yAxis.min !== undefined && yValue < this.config.yAxis.min) {
        this.config.yAxis.min = undefined;
      }
      if (this.config.yAxis.max !== undefined && yValue > this.config.yAxis.max) {
        this.config.yAxis.max = undefined;
      }
    }
  }

  dealTimeLengthAndLocation(xValue: number) {
    if (!this.uiConfig[PropertyKey.TimeSeriesChart_fixedTimeLength].value) {
      if (this.config.xAxis.max !== undefined) {
        delete this.config.xAxis.max;
      }
    } else {
      if (this.uiConfig[PropertyKey.TimeSeriesChart_fixedTimeLocation].value) {
        this.config.xAxis.max = this.uiConfig[PropertyKey.TimeSeriesChart_fixedTimeLength].value + this.uiConfig[PropertyKey.TimeSeriesChart_fixedTimeLocation].value;
      } else {
        if (xValue > this.uiConfig[PropertyKey.TimeSeriesChart_fixedTimeLength].value) {
          const showedMax = dealDataForChartxAxis(xValue);
          this.config.xAxis.max = showedMax;
          this.config.xAxis.min = showedMax - this.uiConfig[PropertyKey.TimeSeriesChart_fixedTimeLength].value;
        }
        
      }
    }
  }

  createVisualMappiecesBySeriesMarker(isAnnotateFailure: boolean) {
    if (!isAnnotateFailure) return;
    for (const [k, v] of this.timeSeryChartSeriesMap.entries()) {
      const visualMapClassMapData = this.visualMapClassMap.get(v.modelId);
      if (!visualMapClassMapData) return;
      visualMapClassMapData.pieces = [];
      if (v.markArea.data.length) {
        for (const [mk, mv] of v.markArea.data.entries()) {
          visualMapClassMapData.pieces.push({
            gte: mv[0].xAxis,
            lte: mv[1].xAxis,
            color: 'red'
          });
          if (visualMapClassMapData.pieces.length === 1 && mv[0].xAxis) { // first
            visualMapClassMapData.pieces.push({
              lt: mv[0].xAxis,
              color: visualMapClassMapData.defaultcolor
            });
          }
          if (v.markArea.data[mk + 1]) { // gap
            visualMapClassMapData.pieces.push({
              gt: mv[1].xAxis,
              lt: v.markArea.data[mk + 1][0].xAxis,
              color: visualMapClassMapData.defaultcolor
            });
          }
          if (mk === v.markArea.data.length - 1) { // right bound
            visualMapClassMapData.pieces.push({
              gt: mv[1].xAxis,
              color: visualMapClassMapData.defaultcolor
            });
          }
        }
      } else {
        // sery's marker no-exist
        const showVariableDataStatusData = this.showVariableDataStatus.get(v.modelId);
        if (showVariableDataStatusData && showVariableDataStatusData.length) {
          const usedData = showVariableDataStatusData[showVariableDataStatusData.length - 1];
          if (usedData.status === 'red') {
            visualMapClassMapData.pieces.push(
              {
                lt: usedData.x - 0.01,
                color: visualMapClassMapData.defaultcolor
              },
              {
                gte: usedData.x - 0.01,
                color: usedData.status === 'red'
              }
            );
          } else { // green
            visualMapClassMapData.pieces.push(
              {
                lte: usedData.x,
                color: visualMapClassMapData.defaultcolor
              },
              {
                gt: usedData.x + 0.01,
                color: visualMapClassMapData.defaultcolor
              }
            );
          }
        }
      }
    }
  }
}

class VisualMapClass {

  seriesIndex: number
  defaultcolor: string

  show = false
  type = 'piecewise'
  pieces = [] as any
  dimension = 0

  constructor(p: {index: number}) {
    this.seriesIndex = p.index;
    this.defaultcolor = '#32a332';
  }
}

class TimeSeryChartSeries {

  name: string
  modelId: string
  step?: string

  type = 'line'
  smooth = true
  color = '#32a332'
  data = []
  markArea = {
    itemStyle: {
      color: 'rgba(255, 173, 177, 0.1)'
    },
    data: [] as any[]
  }

  constructor(p: {name: string, modelId: string, index: number }) {
    this.name = p.name;
    this.modelId = p.modelId;
  }
}
